﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using System.IO;
using snat.util;
using snat.algorithms;

namespace algorithms.algorithms.network.gossip
{

    public class GossipAlgorithm : IAlgorithm, IChartRenderable
    {
        private static Random rand = new Random();

        private int time;
        private int maxTime;

        // the length of a cycle
        private int delta;

        // the current cycle
        private int cycleNumber;

        private List<GossipNode> gossipNodes = new List<GossipNode>();

        private List<double> resultVars;
        private List<double> resultMeans;

        public GossipAlgorithm() {

            time = 0;
            delta = 10;
            cycleNumber = 0;
            maxTime = 50 * delta;

            resultVars = new List<double>();
            resultMeans = new List<double>();

        }

        private void Init() {

            double sum = 0;
            double sumx2 = 0;

            foreach (BaseNode b in NetworkModel.Instance.Nodes.Values.ToList()) {
                GossipNode newNode = new GossipNode(b, gossipNodes, rand.Next(0, delta));
                gossipNodes.Add(newNode);
            }

            foreach (GossipNode gn in gossipNodes) {
                gn.MakeNeighbourhood(gossipNodes);
                gn.UpdateState(rand.Next(0,2000));
                double x = gn.LocalEstimate;
                double x2 = Math.Pow(x, 2.0);
                sum = sum + x;
                sumx2 = sumx2 + x2;
            }

            double mean = 0;
            double meanx2 = 0;

            mean = sum / NetworkModel.Instance.Nodes.Count();
            meanx2 = sumx2 / NetworkModel.Instance.Nodes.Count();

            double var = meanx2 - Math.Pow(mean, 2.0);
            resultVars.Add(var);
            resultMeans.Add(mean);
        }

        public void RunAlgorithm() {

            // Initialise - set up all gossip nodes, and calculate initial variance.
            Init();

            for (int i = 0; i < maxTime; i++) {
                //Console.WriteLine("time is " + i);
                foreach (GossipNode node in gossipNodes) {
                    if (node.NextActivation == time) {
                        // get a random "neighbour"
                        GossipNode neighbour = node.SelectNeighbour();

                        // Calculate average of both local estimates
                        double newEstimate = (node.LocalEstimate + neighbour.LocalEstimate)/2;

                        // Update both nodes with new estimate - this simulates the message passing between nodes
                        node.UpdateState(newEstimate);
                        neighbour.UpdateState(newEstimate);

                        // Calculate some random point in next cycle for node to communicate again
                        int nextActivation = rand.Next(1, delta) + (delta*(cycleNumber+1));
                        node.NextActivation = nextActivation;
                    }
                }

                time++;

                // if its the end of a cycle
                if (time == cycleNumber*delta + delta) {

                    // Calculate the current variance (E(x^2) - E(x)^2)
                    double sum = 0;
                    double sumx2 = 0;

                    foreach (GossipNode node in gossipNodes) {
                        double x = node.LocalEstimate;
                        double x2 = Math.Pow(x, 2.0);
                        sum = sum + x;
                        sumx2 = sumx2 + x2;
                    }

                    double mean = 0;
                    double meanx2 = 0;

                    mean = sum / NetworkModel.Instance.Nodes.Count();
                    meanx2 = sumx2 / NetworkModel.Instance.Nodes.Count();

                    double var = meanx2 - Math.Pow(mean, 2.0);
                    resultVars.Add(var);
                    resultMeans.Add(mean);

                    // update the cycle count
                    cycleNumber = cycleNumber + 1;
                }
            }

            //RenderChart(this);

            
            Console.WriteLine("HERE ARE THE RESULTS OF THE CONVERGENCE THINGAMY JOB");
            Console.WriteLine(resultVars.ToString());
            Console.WriteLine(resultMeans.ToString());

            Array temp1 = resultVars.ToArray();
            Array temp2 = resultMeans.ToArray();

            using (StreamWriter output = new StreamWriter("gossipTwitter.csv")) {
                output.WriteLine("Mean, Var");
                for (int i = 0; i < resultVars.Count; i++) {
                    output.WriteLine("{0}, {1}", resultMeans.ElementAt(i).ToString(), resultVars.ElementAt(i).ToString());
                }
            }
            
        }

        public string Name {
            get { return "Gossip Algorithm"; }
        }

        public void Register() {
            NetworkModel.Instance.TapeEnded += RunAlgorithm;
        }

        public void DeRegister() {
            NetworkModel.Instance.TapeEnded -= RunAlgorithm;
        }

        public DataChart GetRenderableChart() {
            DataSeries series1 = new DataSeries("Parameter Estimate Variance");
            int i = 0;
            foreach (Double var in resultVars) {
                series1.Series.Add(new DataPoint(i, var));
                i++;
            }
            
            DataChartOptions chartOpts = new DataChartOptions("Parameter Aggregation Convergence Chart");
            chartOpts.Description = "This chart plots the cycle number (x axis) versus the variance of each nodes estimate of a globally aggregated parameter";
            return new DataChart(chartOpts, new List<DataSeries> { series1 });
        }

        public event RenderChartDelegate RenderChart;
    }

    internal class GossipNode
    {
        private static Random rand = new Random();

        private BaseNode node;
        public BaseNode Node {
            get
            {
                return node;
            }
        }

        private double localEstimate;
        public double LocalEstimate
        {
            get {
                return localEstimate;
            }
        }

        private int nextActivation;
        public int NextActivation {
            get {
                return nextActivation;
            }
            set {
                nextActivation = value;
            }
        }

        private HashSet<GossipNode> neighbourhood;

        public void MakeNeighbourhood(List<GossipNode> allNodes) {
            int r = 0;
            while (neighbourhood.Count < 5)
            {
                r = rand.Next(0, allNodes.Count - 1);
                if (allNodes.ElementAt(r) != this)
                {
                    neighbourhood.Add(allNodes.ElementAt(r));
                }
            }

        }

        public void UpdateState(double value) {

            localEstimate = value;
        }

        public GossipNode SelectNeighbour() {
            int r = rand.Next(0, neighbourhood.Count - 1);
            
            GossipNode neighbour = neighbourhood.ElementAt(r);

            return neighbour;
        }

        public GossipNode(BaseNode referenceNode, List<GossipNode> allNodes, int firstActivation) {

            neighbourhood = new HashSet<GossipNode>();

            node = referenceNode;

            nextActivation = firstActivation;
        }
    }
}
